package com.zb.security.exception;

import org.springframework.dao.DataAccessException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.PlaintextPasswordEncoder;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;


/**
 * 该类中的方法全部copy于DaoAuthenticationProvider类.只是修改了retrieveUser方法的异常抛出处理部分.从而实现自定义【用户不存在】的异常抛出信息
 * 
 * 【说明】：由于springsecurity自带的认证入口类DaoAuthenticationProvider中的retrieveUser方法不能被重写(具体方法代码如下框中所示).所以loadedUser为null的时候，
 *           返回的 错误信息就始终是“UserDetailsService returned null, which is an interface contract violation”。
 *           所以这里通过自定义的认证类MyDaoAuthenticationProvider，将retrieveUser重写.返回自定义的错误信息.错误信息从国际化资源文件中读取而来.
 *      
 
    *********************************************************************************************************************
    *   protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)   *
    *        throws AuthenticationException {                                                                           *
    *        UserDetails loadedUser;                                                                                    *
    *        try {                                                                                                      *
    *            loadedUser = this.getUserDetailsService().loadUserByUsername(username);                                *
    *        }                                                                                                          *
    *        catch (DataAccessException repositoryProblem) {                                                            *
    *            throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);           *
    *        }                                                                                                          *
    *        if (loadedUser == null) {                                                                                  *
    *            throw new AuthenticationServiceException(                                                              *
    *                    "UserDetailsService returned null, which is an interface contract violation");                 *
    *        }                                                                                                          *
    *        return loadedUser;                                                                                         *
    *    }                                                                                                              *
    *********************************************************************************************************************
        
 * @author zhoubang
 */
@SuppressWarnings("deprecation")
public class MyDaoAuthenticationProvider extends
        AbstractUserDetailsAuthenticationProvider {

    private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();

    private SaltSource saltSource;

    private UserDetailsService userDetailsService;

    private boolean includeDetailsObject = true;

    /**
     * 检查用户是否存在.
     */
    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        UserDetails loadedUser;

        try {
            // 将会调用MyUserDetailService类中的loadUserByUsername方法
            loadedUser = this.getUserDetailsService().loadUserByUsername(
                    username);
        } catch (DataAccessException repositoryProblem) {
            throw new AuthenticationServiceException(
                    repositoryProblem.getMessage(), repositoryProblem);
        }
        // 如果查询该账户名不存在,则抛出异常信息/
        // 这里已经重写了springsecurity默认的认证入口类DaoAuthenticationProvider的retrieveUser方法的异常抛出
        if (loadedUser == null) {
            throw new AuthenticationServiceException(
                    messages.getMessage("DaoAuthentication.userNotFound"));
        }
        System.out.println("loadedUser:" + loadedUser);
        return loadedUser;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        Object salt = null;

        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"), includeDetailsObject ? userDetails
                    : null);
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
                presentedPassword, salt)) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"), includeDetailsObject ? userDetails
                    : null);
        }
    }

    protected void doAfterPropertiesSet() throws Exception {
        Assert.notNull(this.userDetailsService,
                "A UserDetailsService must be set");
    }

    /**
     * 设置密码策略。 如果没有设置，则使用默认的 PlaintextPasswordEncoder
     * 策略，其他的策略比如：Md5PasswordEncoder，表示密码使用md5加密再校验
     * 
     * @author zhoubang 创建时间 ： 2014年12月2日 下午3:32:37
     * @param passwordEncoder
     */
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    protected PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    public void setSaltSource(SaltSource saltSource) {
        this.saltSource = saltSource;
    }

    protected SaltSource getSaltSource() {
        return saltSource;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    protected UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    protected boolean isIncludeDetailsObject() {
        return includeDetailsObject;
    }

    public void setIncludeDetailsObject(boolean includeDetailsObject) {
        this.includeDetailsObject = includeDetailsObject;
    }

}
